a tool for shared writing and social publishing
1import { Metadata } from "next";
2import * as Y from "yjs";
3import * as base64 from "base64-js";
4
5import type { Fact } from "src/replicache";
6import type { Attribute } from "src/replicache/attributes";
7import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
8import { Leaflet } from "./Leaflet";
9import { scanIndexLocal } from "src/replicache/utils";
10import { getRSVPData } from "actions/getRSVPData";
11import { PageSWRDataProvider } from "components/PageSWRDataProvider";
12import { getPollData } from "actions/pollActions";
13import { supabaseServerClient } from "supabase/serverClient";
14import { get_leaflet_data } from "app/api/rpc/[command]/get_leaflet_data";
15import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout";
16import { getPublicationMetadataFromLeafletData } from "src/utils/getPublicationMetadataFromLeafletData";
17
18export const preferredRegion = ["sfo1"];
19export const dynamic = "force-dynamic";
20export const fetchCache = "force-no-store";
21
22type Props = {
23 // this is now a token id not leaflet! Should probs rename
24 params: Promise<{ leaflet_id: string }>;
25};
26export default async function LeafletPage(props: Props) {
27 let { result: res } = await get_leaflet_data.handler(
28 { token_id: (await props.params).leaflet_id },
29 { supabase: supabaseServerClient },
30 );
31 let rootEntity = res.data?.root_entity;
32 if (!rootEntity || !res.data || res.data.blocked_by_admin)
33 return (
34 <NotFoundLayout>
35 <p className="font-bold">Sorry, we can't find this leaflet!</p>
36 <p>
37 This may be a glitch on our end. If the issue persists please{" "}
38 <a href="mailto:contact@leaflet.pub">send us a note</a>.
39 </p>
40 </NotFoundLayout>
41 );
42
43 let [{ data }, rsvp_data, poll_data] = await Promise.all([
44 supabaseServerClient.rpc("get_facts", {
45 root: rootEntity,
46 }),
47 getRSVPData(res.data.permission_token_rights.map((ptr) => ptr.entity_set)),
48 getPollData(res.data.permission_token_rights.map((ptr) => ptr.entity_set)),
49 ]);
50 let initialFacts = (data as unknown as Fact<Attribute>[]) || [];
51 return (
52 <PageSWRDataProvider
53 rsvp_data={rsvp_data}
54 poll_data={poll_data}
55 leaflet_id={res.data.id}
56 leaflet_data={res}
57 >
58 <Leaflet
59 initialFacts={initialFacts}
60 leaflet_id={rootEntity}
61 token={res.data}
62 />
63 </PageSWRDataProvider>
64 );
65}
66
67export async function generateMetadata(props: Props): Promise<Metadata> {
68 let { result: res } = await get_leaflet_data.handler(
69 { token_id: (await props.params).leaflet_id },
70 { supabase: supabaseServerClient },
71 );
72 let rootEntity = res.data?.root_entity;
73 if (!rootEntity || !res.data) return { title: "Leaflet not found" };
74 let publication_data = getPublicationMetadataFromLeafletData(res.data);
75 if (publication_data) {
76 return {
77 title: publication_data.title || "Untitled",
78 description: publication_data.description,
79 };
80 }
81 let { data } = await supabaseServerClient.rpc("get_facts", {
82 root: rootEntity,
83 });
84 let initialFacts = (data as unknown as Fact<Attribute>[]) || [];
85 let scan = scanIndexLocal(initialFacts);
86 let firstPage =
87 scan.eav(rootEntity, "root/page")[0]?.data.value || rootEntity;
88 let pageType = scan.eav(firstPage, "page/type")[0]?.data.value || "doc";
89 let firstBlock, secondBlock;
90 if (pageType === "canvas") {
91 [firstBlock, secondBlock] = scan
92 .eav(firstPage, "canvas/block")
93 .map((b) => {
94 let type = scan.eav(b.data.value, "block/type");
95 if (!type[0]) return null;
96 return {
97 ...b.data,
98 type: type[0].data.value,
99 };
100 })
101 .filter((b) => b !== null)
102 .filter((b) => b.type === "text" || b.type === "heading")
103 .sort((a, b) => {
104 if (a.position.y === b.position.y) {
105 return a.position.x - b.position.x;
106 }
107 return a.position.y - b.position.y;
108 });
109 } else {
110 [firstBlock, secondBlock] = scan
111 .eav(firstPage, "card/block")
112 .map((b) => {
113 let type = scan.eav(b.data.value, "block/type");
114 return {
115 ...b.data,
116 type: type[0]?.data.value,
117 };
118 })
119
120 .filter((b) => b.type === "text" || b.type === "heading")
121 .sort((a, b) => (a.position > b.position ? 1 : -1));
122 }
123 let metadata: Metadata = { title: "Untitled Leaflet", description: " " };
124
125 let titleFact = initialFacts.find(
126 (f) => f.entity === firstBlock?.value && f.attribute === "block/text",
127 ) as Fact<"block/text"> | undefined;
128 if (titleFact) {
129 let doc = new Y.Doc();
130 const update = base64.toByteArray(titleFact.data.value);
131 Y.applyUpdate(doc, update);
132 let nodes = doc.getXmlElement("prosemirror").toArray();
133 metadata.title = YJSFragmentToString(nodes[0]);
134 }
135
136 let descriptionFact = initialFacts.find(
137 (f) => f.entity === secondBlock?.value && f.attribute === "block/text",
138 ) as Fact<"block/text"> | undefined;
139 if (descriptionFact) {
140 let doc = new Y.Doc();
141 const update = base64.toByteArray(descriptionFact.data.value);
142 Y.applyUpdate(doc, update);
143 let nodes = doc.getXmlElement("prosemirror").toArray();
144 metadata.description = YJSFragmentToString(nodes[0]);
145 }
146
147 return metadata;
148}